﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Compilation;
using CodeSandbox;
using CodeSandbox.Contracts;
using CodeSandbox.Controllers;

//[assembly: PreApplicationStartMethod(typeof(DependencyLibrary), "Boot")]
namespace CodeSandbox.Controllers
{
    public class DependencyLibrary:IDependencyLibrary
    {
        static DependencyLibrary()
        {
            Boot();
            Instance = new DependencyLibrary();
        }
        public static IDependencyLibrary Instance { get; private set; }
        private static readonly ConcurrentBag<Type> Types = new ConcurrentBag<Type>();
        private static readonly ConcurrentDictionary<string, object> Index = new ConcurrentDictionary<string, object>();
        public static void Boot()
        {
            var allAssemblyFiles = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}*.dll", Sandbox.RootAssemblyPrefix),SearchOption.AllDirectories);
            var assemblyFiles = allAssemblyFiles.Where(a=> a.Length == allAssemblyFiles.Where(aa=>Path.GetFileName(aa) == Path.GetFileName(a)).Min(l=>l.Length));
            var assemblies = BuildManager.GetReferencedAssemblies().OfType<Assembly>().Where(a => a.GetName().Name.StartsWith(Sandbox.RootAssemblyPrefix)).ToArray();
            var finalAssemblies = assemblyFiles.Select(Assembly.LoadFile).Where(a => assemblies.All(ia => ia.FullName != a.FullName)).ToArray();
            var collectionResult = finalAssemblies.SelectMany(a => a.GetTypes()).All(
                t =>
                {
                    if(!Types.Contains(t)) Types.Add(t); 
                    return true;
                });
            collectionResult = assemblies.SelectMany(a => a.GetTypes()).All(t =>
            {
                if (!Types.Contains(t.DeclaringType == null ? t : t.DeclaringType)) Types.Add(t.DeclaringType==null?t:t.DeclaringType); 
                return true;
            });
            if (!collectionResult) throw new TypeLoadException();
        }

        internal static object GetMapping<TSource, TDestination>()
            where TSource : class
            where TDestination : class,new()
        {
            var mappingType = Types.FirstOrDefault(t => t.GetInterfaces().Any(i=> i.FullName == typeof(IMapRegister<TSource, TDestination>).FullName));
            return mappingType != null ? Activator.CreateInstance(mappingType) : null;
        }

        public TToBeInjected Get<TToBeInjected>(Expression<Func<TToBeInjected, bool>> filter = null, bool alwaysCreateNew = false) where TToBeInjected : class
        {
            var indexKey = string.Format("{0};{1}", typeof(TToBeInjected).AssemblyQualifiedName, filter != null ? filter.Body.ToString() : string.Empty);
            if (!alwaysCreateNew && Index.ContainsKey(indexKey)) return Index[indexKey] as TToBeInjected;
            var matchedTypes = Types.Where(t => t.BaseType == typeof(TToBeInjected)).ToArray(); //_types.Where(t => t.BaseType == typeof(ModelBase) && t.FullName == typeof(TOfType).FullName).ToArray();
            if (!matchedTypes.Any()) matchedTypes = Types.Where(t => t.IsAssignableFrom(typeof(TToBeInjected)) && t.FullName != typeof(TToBeInjected).FullName).ToArray();
            if (!matchedTypes.Any()) matchedTypes = Types.Where(t => t.GetInterfaces().Any(i => i == typeof(TToBeInjected))).ToArray();
            if (!matchedTypes.Any()) matchedTypes = Types.Where(t => t.IsDeepInHerited<TToBeInjected>()).ToArray();
            if (!matchedTypes.Any()) matchedTypes = Types.Where(t => t.IsDeepInterfaced<TToBeInjected>()).ToArray();
            if (!matchedTypes.Any()) return default(TToBeInjected);
            if (matchedTypes.Length == 1)
            {
                var thisType = (TToBeInjected)Activator.CreateInstance(matchedTypes.First());
                if(!alwaysCreateNew) Index.AddOrUpdate(indexKey, thisType, (t, o) => thisType);
                return thisType;
            }
            if (filter == null) throw new ArgumentNullException("filter");
            foreach (var type in matchedTypes)
            {
                var thisType = (TToBeInjected)Activator.CreateInstance(type);
                if (filter.Compile()(thisType))
                {
                    if (!alwaysCreateNew) Index.AddOrUpdate(indexKey, thisType, (t, o) => thisType);
                    return thisType;
                }
                if (!alwaysCreateNew) Index.AddOrUpdate(indexKey, thisType, (t, o) => thisType);
            }
            return null;
        }

        internal static object RunGenericMethod(Type from, object fromObj, string name, Type[] genericParameters, params object[] arguments)
        {
            MethodInfo method = from.GetMethods()
                         .Where(m => m.Name == name)
                         .Select(m => new
                         {
                             Method = m,
                             Params = m.GetParameters(),
                             Args = m.GetGenericArguments()
                         })
                         .Where(x => {
                                var paramTypes = x.Params.Select(p => p.ParameterType.IsGenericParameter? genericParameters[p.ParameterType.GenericParameterPosition]:p.ParameterType).ToArray();
                                var argTypes = arguments.Select(a => a is Type ? a : a.GetType()).ToArray();
                                int index=0;
                                var match = paramTypes.All(pt =>
                                {
                                    if (pt != (Type) argTypes[index])
                                    {
                                        if (pt == typeof(object)) return true;
                                    }
                                    index++;
                                    return true;
                                });
                                var match1 = genericParameters.Length == x.Args.Length;
                                return match && match1;
                         })
                         .Select(x => x.Method.MakeGenericMethod(genericParameters))
                         .First();
            return method.Invoke(fromObj, arguments.Select(a => a is Type ? null : a).ToArray());
        }
    }
}
